Skip to content

feat: add sunxi FEL SPI NOR programmer#21

Open
ArthurHeymans wants to merge 4 commits intomasterfrom
sunxi
Open

feat: add sunxi FEL SPI NOR programmer#21
ArthurHeymans wants to merge 4 commits intomasterfrom
sunxi

Conversation

@ArthurHeymans
Copy link
Owner

Summary

  • Add rflasher-sunxi-fel crate implementing SPI NOR flash programming via the Allwinner FEL (USB boot) protocol
  • Fix write failure at ~53 KiB by using firmware-accelerated batched writes with on-SoC busy-wait
  • Optimize read speed by ~7% using SPI_CMD_FAST for small TX data

Architecture

SunxiFel implements both SpiMaster and OpaqueMaster, wired through HybridFlashDevice (same pattern as Dediprog):

Operation Path Method
Probe, WP, status regs SpiMaster Generic SPI via bytecode
Bulk read OpaqueMaster SPI_CMD_FAST for opcode+addr, RXBUF for data
Bulk write OpaqueMaster Batched FAST(WREN) + TXBUF(PP) + SPINOR_WAIT (~215 pages/batch)
Erase SpiMaster::native_erase_block FAST(WREN) + FAST(erase+addr) + SPINOR_WAIT

Write fix details

The old approach did 3+ separate FEL payload executions per 256-byte page (WREN, PP, then repeated RDSR polling over USB). This failed after ~211 pages (~53 KiB) due to unreliable host-side status polling.

The new approach matches xfel's spinor_helper_write: packs ~215 pages into a single command+swap buffer pair and executes the payload once per batch. SPI_CMD_SPINOR_WAIT handles busy-wait on the SoC, eliminating USB round-trips for status polling. This reduces USB transactions by ~128x.

Core trait changes

  • Added native_erase_block() optional method to SpiMaster (default returns None)
  • Both SpiFlashDevice::erase() and HybridFlashDevice::erase() check it before the generic WREN+erase+RDSR polling path

Test results (D1/F133 + W25Q128, 16 MiB)

Test Result
Probe PASS
1 MiB write + verify PASS
16 MiB read 49.7s (~329 KiB/s, was 53.6s)
16 MiB erase 43s (block-by-block)
16 MiB write Sporadic USB corruption (~0.03%), shared with xfel

The 16 MiB sporadic corruption is a D1/F133 FEL BROM USB reliability issue (xfel hard-errors at 91% on the same test). Not a code bug.

Currently supported SoCs

  • D1/F133 (RISC-V) — payload included

Other Allwinner families (H2/H3, V3s, F1C100s, R528, H616, etc.) need their SPI payload binaries extracted from xfel's chip source files.

…e/erase

Add rflasher-sunxi-fel crate implementing SPI NOR flash programming through
the Allwinner FEL (USB boot) protocol. The programmer uploads a pre-compiled
SPI driver payload to the SoC's SRAM and drives SPI via a bytecode protocol,
matching xfel's approach.

Architecture:
- SpiMaster for probe, status registers, write protection, generic SPI
- OpaqueMaster for firmware-accelerated bulk read/write (via HybridFlashDevice)
- native_erase_block on SpiMaster for on-SoC erase busy-wait

Write acceleration (fixes write failure at ~53 KiB):
- Batched page programming: ~215 pages per payload execution using
  SPI_CMD_FAST (WREN) + SPI_CMD_TXBUF (PP data) + SPI_CMD_SPINOR_WAIT
  (on-SoC busy polling), reducing USB round-trips by ~128x
- Previous approach used 3+ separate payload executions per page with
  host-side status register polling, which failed after ~211 pages

Read optimization:
- SPI_CMD_FAST embeds small TX data (opcode+address) in the command
  buffer, eliminating a separate FEL write per 64 KiB chunk
- ~7% faster reads (49.7s vs 53.6s for 16 MB on D1/F133)

Erase acceleration:
- native_erase_block uses SPI_CMD_FAST + SPI_CMD_SPINOR_WAIT for
  single-execution erase with on-SoC busy-wait

Currently supports D1/F133 (RISC-V). Other SoC families need their
payload binaries extracted from xfel's chip source files.
Following flashprog's architecture where erase is a first-class operation
on the opaque interface (programmers that don't expose raw bus access must
provide their own erase), not on the SPI interface (where generic code
constructs erase sequences from raw SPI commands).

- Remove native_erase_block from SpiMaster trait
- HybridFlashDevice::erase() now tries OpaqueMaster::erase() first;
  falls back to SPI-based WREN+opcode+RDSR polling if it returns Err
- SunxiFel::OpaqueMaster::erase() uses FAST+SPINOR_WAIT bytecodes
  with automatic block size selection (64K/32K/4K)
- Dediprog continues to return Err from OpaqueMaster::erase(),
  triggering the SPI fallback (no behavior change)
- SpiFlashDevice::erase() reverted to the original generic path
  (no native_erase_block concept needed for pure-SPI programmers)
Instead of hardcoding erase opcodes (0xD8/0x52/0x20), SunxiFel now
stores the chip's actual erase block table after probing and uses
select_erase_block() to pick the correct opcode and block size.

This also completes the removal of native_erase_block from SpiMaster.
HybridFlashDevice::erase() now tries OpaqueMaster::erase() first (which
uses the chip's real erase table + SPINOR_WAIT bytecodes), falling back
to SPI-based erase if it returns Err (as Dediprog does).

- Re-export select_erase_block from rflasher_core::flash
- Add set_erase_blocks() on SunxiFel, called after probe in registry
- OpaqueMaster::erase() returns Err if no erase blocks configured,
  gracefully falling back to the SPI path
- rustfmt: reformat chained method call in hybrid_device.rs
- clippy::int_plus_one: simplify cbuf bounds check to < instead of + 1 <=
- clippy::manual_div_ceil: use .div_ceil() in round_up_to_mps
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant